﻿#include "as_stacktrace.h"

#include <iostream>
#include <sstream>
#ifdef _WIN32
#include <windows.h>
#include <DbgHelp.h>
#else
#include <execinfo.h>
#include <cxxabi.h>
#endif

std::string as::tools::PrintTraceStack()
{
	std::ostringstream oss;
	oss << "stack traceback: " << std::endl;

#ifdef _WIN32
	const int MAX_STACK_FRAMES = 5;
	void *pStack[MAX_STACK_FRAMES];
	HANDLE process = GetCurrentProcess();
	SymInitialize(process, NULL, TRUE);
	WORD frames = CaptureStackBackTrace(0, MAX_STACK_FRAMES, pStack, NULL);
	for (WORD i = 0; i < frames; ++i) {
		DWORD64 address = (DWORD64)(pStack[i]);
		DWORD64 displacementSym = 0;
		char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
		PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer;
		pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
		pSymbol->MaxNameLen = MAX_SYM_NAME;
		DWORD displacementLine = 0;
		IMAGEHLP_LINE64 line;
		//SymSetOptions(SYMOPT_LOAD_LINES);
		line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
		if (SymFromAddr(process, address, &displacementSym, pSymbol)
			&& SymGetLineFromAddr64(process, address, &displacementLine, &line)) {
			oss << "\t" << pSymbol->Name << " at " << line.FileName << ":" << line.LineNumber << "(0x" << std::hex << pSymbol->Address << std::dec << ")" << std::endl;
		}
		else {
			oss << "\terror: " << GetLastError() << std::endl;
		}
	}

#else
	const int max_frames = 64;
	void *addrlist[max_frames + 1];
	int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void *));

	if (addrlen == 0) {
		oss << "backtrace addrlen is error";
		return oss.str();
	}

	char **symbollist = backtrace_symbols(addrlist, addrlen);

	for (int i = 1; i < addrlen; ++i) {
		char *begin_name = nullptr;
		char *begin_offset = nullptr;
		char *end_offset = nullptr;

		// Find parentheses and +address offset surrounding the mangled name
		for (char *p = symbollist[i]; *p; ++p) {
			if (*p == '(') {
				begin_name = p;
			}
			else if (*p == '+') {
				begin_offset = p;
			}
			else if (*p == ')' && begin_offset) {
				end_offset = p;
				break;
			}
		}

		if (begin_name && begin_offset && end_offset && begin_name < begin_offset) {
			*begin_name++ = '\0';
			*begin_offset++ = '\0';
			*end_offset = '\0';

			int status;
			char *demangled = abi::__cxa_demangle(begin_name, nullptr, nullptr, &status);
			if (status == 0) {
				oss << symbollist[i] << " : " << demangled << "+" << begin_offset << std::endl;
			}
			else {
				// Unable to demangle the symbol, but still print the mangled version
				oss << symbollist[i] << " : " << begin_name << "+" << begin_offset << std::endl;
			}
			free(demangled);
		}
		else {
			// Couldn't parse the line, print the whole line
			oss << symbollist[i] << std::endl;
		}
		free(symbollist);
	}
#endif
	return oss.str();
}

